/**
  * @file
  * video decoding with libavcodec API example
  *
  * decode_video.c
  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef void (*VideoCallback)(unsigned char *data_y, unsigned char *data_u, unsigned char *data_v, int line1, int line2, int line3, int width, int height, long pts);

#include <libavcodec/avcodec.h>

#define INBUF_SIZE 4096

typedef enum ErrorCode
{
	kErrorCode_Success = 0,
	kErrorCode_Invalid_Param,
	kErrorCode_Invalid_State,
	kErrorCode_Invalid_Data,
	kErrorCode_Invalid_Format,
	kErrorCode_NULL_Pointer,
	kErrorCode_Open_File_Error,
	kErrorCode_Eof,
	kErrorCode_FFmpeg_Error
} ErrorCode;

typedef enum LogLevel
{
	kLogLevel_None, //Not logging.
	kLogLevel_Core, //Only logging core module(without ffmpeg).
	kLogLevel_All	//Logging all, with ffmpeg.
} LogLevel;

typedef enum DecoderType
{
	kDecoderType_H264,
	kDecoderType_H265
} DecoderType;

LogLevel logLevel = kLogLevel_None;
DecoderType decoderType = kDecoderType_H265;

void simpleLog(const char *format, ...)
{
	if (logLevel == kLogLevel_None)
	{
		return;
	}

	char szBuffer[1024] = {0};
	char szTime[32] = {0};
	char *p = NULL;
	int prefixLength = 0;
	const char *tag = "Core";

	prefixLength = sprintf(szBuffer, "[%s][%s][DT] ", szTime, tag);
	p = szBuffer + prefixLength;

	if (1)
	{
		va_list ap;
		va_start(ap, format);
		vsnprintf(p, 1024 - prefixLength, format, ap);
		va_end(ap);
	}

	printf("%s\n", szBuffer);
}

void ffmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl)
{
	static int printPrefix = 1;
	static int count = 0;
	static char prev[1024] = {0};
	char line[1024] = {0};
	static int is_atty;
	AVClass *avc = ptr ? *(AVClass **)ptr : NULL;
	if (level > AV_LOG_DEBUG)
	{
		return;
	}

	line[0] = 0;

	if (printPrefix && avc)
	{
		if (avc->parent_log_context_offset)
		{
			AVClass **parent = *(AVClass ***)(((uint8_t *)ptr) + avc->parent_log_context_offset);
			if (parent && *parent)
			{
				snprintf(line, sizeof(line), "[%s @ %p] ", (*parent)->item_name(parent), parent);
			}
		}
		snprintf(line + strlen(line), sizeof(line) - strlen(line), "[%s @ %p] ", avc->item_name(ptr), ptr);
	}

	vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl);
	line[strlen(line) + 1] = 0;
	simpleLog("%s", line);
}

VideoCallback videoCallback = NULL;

/// 发送 包 给 解码器
static int send_package(AVCodecContext *avctx, const AVPacket *avpkt, AVFrame *frame)
{
    int ret = 0;
    while (1)
    {
        ret = avcodec_send_packet(avctx, avpkt);

        // 成功地发送了包
        if (ret == 0)
            return 0;

		// 当前, 输入是无法访问的, 用户必须用 avcodec_receive_frame 读完输出, 才能继续执行 avcodec_send_packet
		if (ret == AVERROR(EAGAIN))
		{
			avcodec_flush_buffers(avctx);
			continue;
			// while (1)
			// {
			//     // 不停的读输出
			//     ret = avcodec_receive_frame(avctx, frame);
			//     // 成功地读到一帧
			//     if (ret == 0)
			//         continue;
			//     // 读到了结束
			//     if (ret == AVERROR(EAGAIN))
			//         ret = 0;
			//     // 非0值都应该是错误, 退出
			//     break;
			// }
			// // 读完输出了, 继续上一个 while 循环, 即 继续执行: avcodec_send_packet
			// if (ret == 0)
			//     continue;

			// // 遇到错误了退出
			// simpleLog("avcodec_receive_frame failed, ret: %d", ret);
			// return ret;
		}
		// EOF 的话, 表示是flush, 直接退出就可以了
		if (ret == AVERROR_EOF)
			return ret;
		simpleLog("avcodec_send_packet failed, ret: %d", ret);
		// 其它错误都要退出
		return ret;
    }
    return 0;
}

/// 从 解码器 接收 帧
static int receive_frame(AVCodecContext *avctx, const AVPacket *avpkt, AVFrame *frame)
{
    int ret = 0;
    while (1)
    {
        ret = avcodec_receive_frame(avctx, frame);

        // 成功地读到一帧
        if (ret == 0)
            return 0;

        // 此时, 输出是无效的, 必须重新发送输入
        if (ret == AVERROR(EAGAIN))
        {
            ret = avcodec_send_packet(avctx, avpkt);

            // 只有发送成功了, 就继续执行: avcodec_receive_frame
            if (ret == 0)
                continue;

            // 其它的ret, 都应该认为是错误.
            simpleLog("avcodec_send_packet failed, ret: %d", ret);
            return ret;
        }
        
		// EOF 的话, 表示是flush, 直接退出就可以了
        if (ret == AVERROR_EOF)
            return ret;
        
        // 其它的ret, 都应该认为是错误.
		simpleLog("avcodec_receive_frame failed, ret: %d", ret);
        return ret;
    }
    return 0;
}

static ErrorCode decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt)
{
	int ret;

	if ((ret = send_package(dec_ctx, pkt, frame)) != 0)
	{
		simpleLog("send_package failed");
		return ret;
	}

	if ((ret = receive_frame(dec_ctx, pkt, frame)) != 0)
	{
		simpleLog("receive_frame failed");
		return ret;
	}

	// ret = copyFrameData(frame, outFrame, ptslist);
	// if (ret != kErrorCode_Success)
	// {
	// 	simpleLog("Error copyFrameData failed, ret: %d", ret);
	// 	break;
	// }
	
	videoCallback(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], frame->linesize[1], frame->linesize[2], frame->width, frame->height, frame->pts);
	// simpleLog("callback done");

	return ret;
}

int isInit = 0;
const AVCodec *codec;
AVCodecParserContext *parser;
AVCodecContext *c = NULL;
AVPacket *pkt;
AVFrame *frame;
AVFrame *outFrame;
long ptslist[10];

ErrorCode openDecoder(int codecType, long callback, int logLv)
{
	ErrorCode ret = kErrorCode_Success;
	do
	{
		simpleLog("Initialize decoder.");

		if (isInit != 0)
		{
			break;
		}

		decoderType = codecType;
		logLevel = logLv;

		if (logLevel == kLogLevel_All)
		{
			av_log_set_callback(ffmpegLogCallback);
		}

		/* find the video decoder */
		if (decoderType == kDecoderType_H264)
		{
			codec = avcodec_find_decoder(AV_CODEC_ID_H264);
		}
		else
		{
			codec = avcodec_find_decoder(AV_CODEC_ID_H265);
		}

		if (!codec)
		{
			simpleLog("Error Codec not found");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		parser = av_parser_init(codec->id);
		if (!parser)
		{
			simpleLog("Error parser not found");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		c = avcodec_alloc_context3(codec);
		if (!c)
		{
			simpleLog("Error Could not allocate video codec context");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		if (avcodec_open2(c, codec, NULL) < 0)
		{
			simpleLog("Error Could not open codec");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		frame = av_frame_alloc();
		if (!frame)
		{
			simpleLog("Error Could not allocate video frame");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		outFrame = av_frame_alloc();
		if (!outFrame)
		{
			simpleLog("Error Could not allocate video frame");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		pkt = av_packet_alloc();
		if (!pkt)
		{
			simpleLog("Error Could not allocate video packet");
			ret = kErrorCode_FFmpeg_Error;
			break;
		}

		for (int i = 0; i < 10; i++)
		{
			ptslist[i] = LONG_MAX;
		}

		videoCallback = (VideoCallback)callback;

	} while (0);
	simpleLog("Decoder initialized %d.", ret);
	return ret;
}

ErrorCode decodeData(unsigned char *data, size_t data_size, long pts)
{
	ErrorCode ret = kErrorCode_Success;

	pkt->data = data;
	pkt->size = data_size;

	ret = decode(c, frame, pkt);
	return ret;
}

ErrorCode flushDecoder()
{
	/* flush the decoder */
	return decode(c, frame, NULL);
}

ErrorCode closeDecoder()
{
	ErrorCode ret = kErrorCode_Success;

	do
	{
		if (parser != NULL)
		{
			av_parser_close(parser);
			simpleLog("Video codec context closed.");
		}
		if (c != NULL)
		{
			avcodec_free_context(&c);
			simpleLog("Video codec context closed.");
		}
		if (frame != NULL)
		{
			av_frame_free(&frame);
		}
		if (pkt != NULL)
		{
			av_packet_free(&pkt);
		}
		// if (yuvBuffer != NULL)
		// {
		// 	av_freep(&yuvBuffer);
		// }
		if (outFrame != NULL)
		{
			av_frame_free(&outFrame);
		}
		simpleLog("All buffer released.");
	} while (0);

	return ret;
}

int callbackIndex = 0;
void vcb_frame(unsigned char *data_y, unsigned char *data_u, unsigned char *data_v, int line1, int line2, int line3, int width, int height, long pts)
{
	simpleLog("[%d]In video call back, size = %d * %d, pts = %ld", ++callbackIndex, width, height, pts);
	int i = 0;
	unsigned char *src = NULL;
	FILE *dst = fopen("vcb.yuv", "a");
	for (i = 0; i < height; i++)
	{
		src = data_y + i * line1;
		fwrite(src, width, 1, dst);
	}

	for (i = 0; i < height / 2; i++)
	{
		src = data_u + i * line2;
		fwrite(src, width / 2, 1, dst);
	}

	for (i = 0; i < height / 2; i++)
	{
		src = data_v + i * line3;
		fwrite(src, width / 2, 1, dst);
	}

	fclose(dst);
}

int main(int argc, char **argv)
{
	//		openDecoder(1, vcb_frame, kLogLevel_Core);
	//
	//		//const char* filename = "Forrest_Gump_IMAX.h264";
	//		const char* filename = "FourPeople_1280x720_60_1M.265";
	//
	//		FILE* f;
	//
	//		//   uint8_t *data;
	//		size_t   data_size;
	//
	//		//uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	//		///* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
	//		//memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
	//
	//		char* buffer;
	//		buffer = (char*)malloc(sizeof(char) * (INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE));
	//		if (buffer == NULL) {
	//			simpleLog("Memory error");
	//			exit(2);
	//		}
	//
	//		f = fopen(filename, "rb");
	//		if (!f) {
	//			simpleLog("Could not open %s", filename);
	//			exit(1);
	//		}
	//
	//		while (!feof(f)) {
	//			/* read raw data from the input file */
	//			//data_size = fread(inbuf, 1, INBUF_SIZE, f);
	//			data_size = fread(buffer, 1, INBUF_SIZE, f);
	//
	//			if (!data_size)
	//				break;
	//
	//			/* use the parser to split the data into frames */
	//			//data = inbuf;
	//			decodeData(buffer, data_size, 0);
	//		}
	//		fclose(f);
	//		free(buffer);
	//
	//		flushDecoder();
	//		closeDecoder();

	return 0;
}
